package com.example.lin.handler;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.example.lin.common.LoginResult;
import com.example.lin.common.RedisService;
import com.example.lin.entity.vo.LoginUser;
import com.example.lin.enums.Result;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录认证成功处理
 * 登录认证失败处理
 * 登出成功处理
 * 请求未认证处理
 * session过期处理
 * 权限不足处理
 *
 * @author Administrator
 */
@Slf4j
@Component
public class SecurityHandler implements
        AuthenticationSuccessHandler,
        AuthenticationFailureHandler,
        LogoutSuccessHandler,
        AuthenticationEntryPoint,
        SessionInformationExpiredStrategy,
        AccessDeniedHandler
{

    @Resource
    private RedisService redisService;

    public void writeResponse(HttpServletResponse response, String json) throws IOException {
        // 返回前端
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

    public void writeResponse(HttpServletResponse response, int code, String msg) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("msg", msg);
        String json = JSON.toJSONString(map);
        writeResponse(response, json);
    }

    /**
     * 认证成功处理
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 获取用户身份信息
        LoginUser user = (LoginUser) authentication.getPrincipal();
        if (ObjectUtils.isEmpty(user)) {
            log.error("认证成功：[{}]", "用户不存在");
            throw new RuntimeException("用户不存在");
        }
        StpUtil.login(user.getUsername());
        // 获取token
        String token = StpUtil.getTokenValue();
        // 获取token时间
        long timeout = StpUtil.getTokenTimeout();
        // 将token存入redis
        redisService.set("username:" + user.getUsername(), token, timeout);
        // 输出登录成功日志
        log.info("登录成功");
        LoginResult result = new LoginResult(user.getUser().getId(), Result.SUCCESS.getCode(), token, timeout);
        String json = JSON.toJSONString(result);
        writeResponse(response, json);
    }

    /**
     * 认证失败处理
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 输出登录失败日志
        log.error("认证失败：[{}]", exception.getMessage());
        writeResponse(response, 403, "用户名或密码错误");
    }

    /**
     * 登出成功处理
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        LoginUser principal = (LoginUser) authentication.getPrincipal();
        try {
            if (!ObjectUtils.isEmpty(principal)) {
                // 输出登出成功日志
                log.info("登出成功");
                // 删除redis中的用户信息
                redisService.del("username:" + principal.getUsername());
                // 清除SecurityContextHolder中的用户信息
                SecurityContextHolder.clearContext();
                // 清除cookie
                StpUtil.logout();
                writeResponse(response, 200, "登出成功");
            }
        } catch (RuntimeException e) {
            log.error("用户未登录");
            throw new RuntimeException("用户未登录");
        }
    }

    /**
     * 请求未认证处理
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        // 输出认证失败日志
        log.error("未登录：[{}]", authException.getMessage());
        writeResponse(response, 401, "未登录");
    }

    /**
     * session过期处理
     */
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        // 输出session过期日志
        log.warn("该账号已在其它设备登录");
        writeResponse(event.getResponse(), 401, "该账号已在其它设备登录");
    }

    /**
     * 权限不足处理
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        // 输出权限不足日志
        log.error("权限不足");
        writeResponse(response, Result.FORBIDDEN.getCode(), Result.FORBIDDEN.getMsg());
    }
}
